<?php

namespace Services\Request;

use Services\Log\LogService;
use Services\Request\Exceptions\RequestException;
use Services\Sign\SignatureService;
use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface;
use Think\Log;

abstract class BaseRequest
{
    public $logType = 'BaseRequest';
    /**
     * 接口基础url
     * @var string
     */
    public $baseUri = '';

    /**
     * 业务侧响应成功状态码,默认0表示成功,非0则失败
     * @var int
     */
    public $successCode = 0;
    /**
     * 错误信息字段名
     * @var string
     */
    public $errorMsgField = 'error';

    /**
     * 请求超时时间
     * @var int
     */
    public $timeout = 5;

    /**
     * 是否开启调试
     * @var bool
     */
    public $debug = true;

    /**
     * 是否自动加签
     * @var bool
     */
    public $sign = true;

    /**
     * 请求头
     * @var array
     */
    public $headers = [];

    /**
     * 初始化guzzle的配置
     * @var array
     */
    public $clientConfig = [];

    /**
     * 响应的header头
     * @var array
     */
    public $responseHeader = [];


    /**
     * 接口path
     * @var string
     */
    private $uri;

    /**
     * 请求参数
     * @var array
     */
    private $params = [];
    /**
     * 请求client参数
     * @var array
     */
    private $requestOptions = [];

    /**
     * 请求方式
     * @var string
     */
    private $method = 'get';

    /**
     * 响应结果
     * @var mixed
     */
    private $content = [];

    /**
     * 原始响应结果
     * @var mixed
     */
    private $body = '';

    /**
     * GuzzleHttp实例
     * @var Client
     */
    private $client;

    /**
     * 响应实例
     * @var
     */
    private $response;

    /**
     * 设置请求基础url
     * @return string
     */
    abstract public function setBaseUri();

    /**
     * @return array
     */
    public function getRequestOptions(): array
    {
        return $this->requestOptions;
    }

    /**
     * @param array $requestOptions
     */
    public function setRequestOptions(array $requestOptions): void
    {
        $this->requestOptions = $requestOptions;
    }



    /**
     * 构建请求
     * @param string $url
     * @param array $params
     * @param string $method
     * @return mixed
     * @throws \Exception
     */
    protected function buildRequest(string $url, array $params, string $method = 'post')
    {
        try {
            $this->setUri($url);
            $this->setMethod($method);
            $this->setParams($params);
            $this->exec();
        } catch (\Exception $e) {
            LogService::write(format_exception($e), 'buildRequest err', Log::ERR);
            throw new \Exception($e->getMessage(), $e->getCode());
        }
        return $this->getContent();
    }

    /**
     * 请求通道
     * @return $this
     * @throws \Throwable
     */
    public function exec()
    {
        $this->initBaseUri();
        $this->initClient();
        $this->requestWithSign();
        $this->request();
        $this->parseResponse();

        return $this;
    }

    /**
     * 初始化请求基础url
     * @throws \Exception
     */
    public function initBaseUri()
    {
        if (empty($this->setBaseUri()) && empty($this->baseUri)) {
            throw new \Exception('请设置请求基础url');
        }

        if (empty($this->baseUri)) {
            $this->baseUri = $this->setBaseUri();
        }
    }

    /**
     * 初始化请求
     * @return void
     */
    public function initClient()
    {
        // 可根据后续需要扩展参数
        $this->client = new Client(array_merge([
            'base_uri' => $this->baseUri,
            'debug' => $this->debug,
            'timeout' => $this->timeout,
            'headers' => $this->headers,
        ], $this->clientConfig));
    }

    /**
     * 对请求加签
     * @return void
     */
    public function requestWithSign()
    {
        if ($this->sign) {
            $this->params = array_merge($this->params, [
                'sign' => SignatureService::generate($this->params, $this->rpcSignSecret()),
            ]);
        }
    }

    /**
     * 获取签名secret
     * @return string
     */
    public function rpcSignSecret()
    {
        //todo：暂时写死，后续从配置文件中读取
        return 'hsg250308';
//        return config('rpc_request.common_rpc_key');
    }

    /**
     * 执行请求
     * @return void
     * @throws \GuzzleHttp\Exception\GuzzleException
     * @throws \Throwable
     */
    public function request(): void
    {
        try {
            $log = [
                'base_url' => $this->baseUri,
                'url' => $this->uri,
                'headers' => $this->headers,
                'params' => $this->params,
                'method' => $this->method,
            ];
            //请求前
            LogService::write($log, 'request_before');
            $this->response = $this->dispatchRequest();
            if (empty(arr_get($this->clientConfig, 'stream'))) {
                //原始响应
                $this->body = $this->response->getBody()->getContents();
                //响应header头
                $this->responseHeader = $this->response->getHeaders();
                //格式化原始响应
                $result = json_decode($this->body, true);
                $this->content = $result ?? [];
                //请求后
                LogService::write(array_merge($log, ['body' => $this->body, 'content' => $this->content]), 'request_after');
            } else {
                $this->content = $this->response;
            }
        } catch (\Throwable $exception) {
            $log = [
                'base_url' => $this->baseUri,
                'url' => $this->uri,
                'headers' => $this->headers,
                'params' => $this->params,
                'method' => $this->method,
                'body' => $this->body,
                'content' => $this->content,
                'message' => $exception->getMessage(),
            ];
            LogService::write($log, 'request_error');
            throw $exception;
        }
    }

    /**
     * @return ResponseInterface
     * @throws RequestException
     * @throws \GuzzleHttp\Exception\GuzzleException
     */
    private function dispatchRequest()
    {
        $method = strtolower($this->method);
        $response = null;
        switch ($method) {
            case 'get':
                $response = $this->client->get($this->uri, ['query' => $this->params]);
                break;
            case 'post':
                $response = $this->client->post($this->uri, ['form_params' => $this->params]);
                break;
            case 'multipart':
                $response = $this->client->post($this->uri, ['multipart' => $this->params]);
                break;
            case 'json':
                $response = $this->client->post($this->uri, ['json' => $this->params]);
                break;
            case 'postv2':
                $response = $this->client->post($this->uri, $this->requestOptions);
                break;
            default:
                throw new RequestException('不支持的请求方法');
                break;
        }
        return $response;
    }

    /**
     * 处理响应
     * @return void
     * @throws \Exception
     */
    public function parseResponse()
    {
        try {
            $result = $this->getContent();

            //自由处理业务响应的错误示例:
            if (!isset($result['code']) || $result['code'] !== $this->successCode) {
                throw new \Exception($result[$this->errorMsgField] ?? '未知错误');
            }

            $this->content = $result['data'] ?? [];
        } catch (\Throwable $exception) {
            LogService::write([
                'base_uri' => $this->getBaseUri(),
                'url' => $this->getUri(),
                'headers' => $this->getHeaders(),
                'params' => $this->getParams(),
                'method' => $this->getMethod(),
                'body' => $this->getBody(),
                'content' => $this->getContent(),
                'message' => $exception->getMessage(),
            ], 'request_response_error');
            throw $exception;
        }
    }

    /**
     * 设置当前请求超时时间
     * @param int $timeout
     * @return $this
     */
    public function setTimeOut(int $timeout)
    {
        $this->timeout = $timeout;

        return $this;
    }

    /**
     * 设置当前请求头
     * @param array $headers
     * @return $this
     */
    public function setHeaders(array $headers = [])
    {
        $this->headers = $headers;

        return $this;
    }

    /**
     * 设置guzzle请求注入client的config配置
     * @param array $clientConfig
     * @return $this
     */
    public function setClient(array $clientConfig = [])
    {
        $this->clientConfig = $clientConfig;

        return $this;
    }

    /**
     * 设置请求path
     * @param $uri
     * @return $this
     */
    public function setUri($uri)
    {
        $this->uri = $uri;

        return $this;
    }

    /**
     * 设置请求参数
     * @param $params
     * @return $this
     */
    public function setParams($params)
    {
        $this->params = $params;

        return $this;
    }

    /**
     * 设置请求方式
     * @param $method
     * @return $this
     */
    public function setMethod($method)
    {
        $this->method = $method;

        return $this;
    }

    /**
     * 获取接口基础url
     * @return string
     */
    public function getBaseUri()
    {
        return $this->baseUri;
    }

    /**
     * 获取请求path
     * @return string
     */
    public function getUri()
    {
        return $this->uri;
    }

    /**
     * 获取请求头
     * @return array
     */
    public function getHeaders()
    {
        return $this->headers;
    }

    /**
     * 获取请求参数
     * @return array
     */
    public function getParams()
    {
        return $this->params;
    }

    /**
     * 获取请求方式
     * @return string
     */
    public function getMethod()
    {
        return $this->method;
    }

    /**
     * 获取原始响应
     * @return mixed
     */
    public function getBody()
    {
        return $this->body;
    }

    /**
     * 获取响应结果
     * @return mixed
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * 获取响应的header
     * @return array
     */
    public function getResponseHeader()
    {
        return $this->responseHeader;
    }

    public function setContent($content)
    {
        $this->content = $content;
    }
}
